﻿using System;
using System.Reflection;
using Fuse8.DomainFramework.Practices.SystemFacade.Extensions;

namespace Fuse8.DomainFramework.Practices.SystemFacade
{
	internal class Converter
	{
		static object GetDefaultValue (Type type)
		{
			if (type.IsValueType)
			{
				return Activator.CreateInstance(type);
			}
			return null;
		}

		static public object ChangeType(object value, Type conversionType)
		{
			return value == null ? GetDefaultValue(conversionType) : ChangeType(value, value.GetType(), conversionType);
		}

		static public object ChangeTypeGeneric<TFrom, TTo>(object value)
		{
			return ChangeType(value, typeof(TFrom), typeof(TTo));
		}

		static public object ChangeType(object value, Type typeFrom, Type typeTo)
		{
			if (value == null)
			{
				return GetDefaultValue(typeTo);
			}

			if (typeTo.IsEnum)
			{
				return ConvertToEnum(value, typeFrom, typeTo);
			}

			if (typeFrom.IsEnum)
			{
				return typeTo == typeof(string) ? value.ToString() : ChangeType(Convert.ChangeType(value, Enum.GetUnderlyingType(typeFrom)), Enum.GetUnderlyingType(typeFrom), typeTo);
			}

			if (typeTo == typeof(Guid))
			{
				return new Guid(value.ToString());
			}
			
			if (typeTo == typeof(TimeSpan))
			{
				return TimeSpan.Parse(value.ToString());
			}

			var isFromNullable = typeFrom.IsNullable();
			var isToNullable = typeTo.IsNullable();

			if (isFromNullable && !isToNullable)
			{
				return ChangeType(value, Nullable.GetUnderlyingType(typeFrom), typeTo);
			}

			if (isToNullable)
			{
				var ut = Nullable.GetUnderlyingType(typeTo);
				return ut.IsEnum ? ConvertToEnum(value, typeFrom, ut) : ChangeType(value, typeFrom, ut);
			}

			return Convert.ChangeType(value, typeTo);
		}

		public static string ObjectToString(object obj)
		{
			if (obj == null)
			{
				return null;
			}
			return obj.ToString();
		}

		public static Guid StringToGuid(string str)
		{
			if (string.IsNullOrEmpty(str))
			{
				return Guid.Empty;
			}
			return new Guid(str);
		}

		internal static TEnum ToEnum<TEnum, TUnder>(object obj)
		{
			if (obj is string)
			{
				var str = obj.ToString();
				return (TEnum)Enum.Parse(typeof(TEnum), str);
			}
			return (TEnum)Convert.ChangeType(obj, typeof(TUnder));
		}

		public static MethodInfo GetConversionMethod(Type from, Type to)
		{
			if (from == null || to == null)
			{
				return null;
			}

			if (to == typeof(string))
			{
				return typeof(Converter).GetMethod("ObjectToString", BindingFlags.Static | BindingFlags.Public);
			}

			if (to.IsEnum)
			{
				return typeof(Converter)
					.GetMethod("ToEnum", BindingFlags.Static | BindingFlags.NonPublic)
					.MakeGenericMethod(to, Enum.GetUnderlyingType(to));
			}

			if (IsComplexConvert(from) || IsComplexConvert(to))
			{
				return
					typeof(Converter)
						.GetMethod(
							"ChangeTypeGeneric",
							BindingFlags.Static | BindingFlags.Public
						)
						.MakeGenericMethod(from, to);
			}
			return null;
		}

		private static bool IsComplexConvert(Type type)
		{
			if (type.IsEnum)
			{
				return true;
			}
			if (type.IsNullable())
			{
				if (Nullable.GetUnderlyingType(type).IsEnum)
				{
					return true;
				}
			}
			return false;
		}

		static private object ConvertToEnum(object value, Type typeFrom, Type typeTo)
		{
			if (!typeFrom.IsEnum)
			{
				if (typeFrom == typeof(string))
				{
					return Enum.Parse(typeTo, value.ToString());
				}
			}
				return Enum.ToObject(typeTo, Convert.ChangeType(value, Enum.GetUnderlyingType(typeTo)));
		}
	}
}